package com.then.basic.socket.core.reader;

import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.security.InvalidParameterException;
import java.util.Arrays;


import android.util.Log;

import com.then.basic.socket.SocketConfig;
import com.then.basic.socket.core.ClientRequest;

/**
 *
 * Created by longyi on 2016/12/9.
 */

public class ProtoStreamReader {
    private static final String TAG = "ProtoStreamReader";
    private static final int PackageFirstHeadTagLen = 10;

    private InputStream inputStream;

    private static final byte[] PACKAGE_HEAD_FLAGS = {
            SocketConfig.HEADER_INDICATER_0,
            SocketConfig.HEADER_INDICATER_1,
            SocketConfig.HEADER_INDICATER_2,
            SocketConfig.HEADER_INDICATER_3
    };

    public ProtoStreamReader(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    public ClientRequest readObj() throws ReadSocketException {
        if (inputStream == null) {
            throw new InvalidParameterException("inputStream is null!");
        }

        // parse package
        return new ClientRequest(readPackage(inputStream));
    }

    private byte[] readPackage(InputStream inputStream) throws ReadSocketException {
        // read head
        byte[] header = new byte[PackageFirstHeadTagLen];
        int packageLength = getPackageDataLength(inputStream, header);
        if (packageLength < PackageFirstHeadTagLen) {  // check package length
            Log.e(TAG, "readBody: invalid package size!");
            throw new ReadSocketPackageDataException("invalid package size:" + packageLength);
        }

        // read package contents
        byte[] contents = Arrays.copyOf(header, packageLength);
        if (readByteArray(inputStream, contents, PackageFirstHeadTagLen, packageLength - PackageFirstHeadTagLen)
                != packageLength - PackageFirstHeadTagLen) {
            Log.e(TAG, "readPackage: EOF");
            throw new ReadSocketEOFException("EOF reached when read body");
        }

        return contents;
    }

    private int getPackageDataLength(InputStream inputStream, byte[] headBuffer) throws ReadSocketException {
        int read = readByteArray(inputStream, headBuffer, 0, PackageFirstHeadTagLen);
        if (read != PackageFirstHeadTagLen) { // read part of data
            throw new ReadSocketEOFException();
        }
        if (!checkFirstPackageHeadTag(headBuffer)) {
            Log.e(TAG, "check tag head failed!");
            throw new ReadSocketPackageDataException("check tag failed!");
        }
        return getPackageSize(headBuffer);
    }

    private boolean checkFirstPackageHeadTag(byte[] temp) {
        if (temp == null || temp.length < PACKAGE_HEAD_FLAGS.length) {
            return false;
        }
        for (int i = 0; i < PACKAGE_HEAD_FLAGS.length; i++) {
            if (PACKAGE_HEAD_FLAGS[i] != temp[i]) {
                return false;
            }
        }
        return true;
    }

    private int getPackageSize(byte[] temp) {
        return ((temp[SocketConfig.SRP_SIZE_HIGH] & 0xff) << 8) | (temp[SocketConfig.SRP_SIZE_LOW] & 0xff);
    }

    /**
     * read data to buffer
     * @param inputStream stream to read from
     * @param array read data
     * @return data's length already read.
     * @throws ReadSocketException
     */
    private int readByteArray(InputStream inputStream, byte[] array, int offset, int length)
            throws ReadSocketException {
        if (array == null || array.length < offset + length) {
            throw new InvalidParameterException("array length not enough!");
        }

        int totalRead = 0;
        while (totalRead < length) {
            int read = 0;
            try {
                read = inputStream.read(array, offset + totalRead, length - totalRead);
            } catch (IOException e) {
                if (e instanceof SocketTimeoutException) {
                    Log.e(TAG, "readByteArray: SocketTimeoutException");
                } else {
                    Log.e(TAG, "readByteArray: IOException");
                }
                throw new ReadSocketIOException();
            }
            if (read != -1) {
                totalRead += read;
            } else { // EOF, return immediately
                Log.w(TAG, "readByteArray: EOF reached");
                return totalRead;
            }
        }
        return totalRead; // read all succeed.
    }
}
